<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title> new document </title>
<meta name="author" content="" />
<meta name="keywords" content="" />
<meta name="description" content="" />
<style>
body{margin:0;padding:10px}
#test{ width:200px;margin:0;padding:0;height:20px;}
#ac{position:absolute;z-index:999;left:10px;top:34px;width:202px;border:solid 1px #ccc;display:none}
#ac ul{ margin:0;padding:0;list-style:none;width:100%;list-style-position:outside;}
#ac ul li{cursor:pointer;height:20px;line-height:20px;white-space:nowrap;width:100%;overflow:hidden;text-overflow:ellipsis;-o-text-overflow:ellipsis}
#ac ul li.curr{ background:#ccc}
#log{min-height:20px; overflow:auto; border:solid 1px red;margin-left:250px;}
</style>
</head>

<body>
<form name="autocomplete" action="">
<input type="text" name="test" id="test" />
<input type="submit" value="提交" name="submit" />
<input type="button" value="clear" autocomplete="off" name="clear" id="clear" /><!---->
</form>
<div id="ac"><ul></ul></div>
<div id="log"></div>
<script src="../lib/jquery-1.4.2.js"></script>
<script>//<![CDATA[
function Cache(){
	this.cache = {};
}
Cache.prototype.set = function( key, value, isObject){
	if( isObject ){
		if( window.JSON && JSON.stringify){
			this.cache[ key ] = JSON.stringify( value);
		}else{
			throw new Error( 'don\'t support JSON.stringify function');
		}
	}else{
		this.cache[ key ] = value;
	}
};
Cache.prototype.get = function( key, isObject ){
	if( window.JSON && JSON.parse ){
		return this.cache[ key] ? 
			( isObject ?  JSON.parse( this.cache[ key]) : this.cache[ key] ) :
			null;
	}else{
		return this.cache[ key] ? 
			( isObject ?  ( new Function( 'return ' + this.cache[ key] ) )() : this.cache[ key] ) :
			null;
	}
};
Cache.prototype.has = function( key){
	return this.cache.hasOwnProperty( key ) ? true : false;
}
Cache.prototype.remove = function( keys){
	for( var key in keys){
		if( this.cache.hasOwnProperty( key) ){
			delete this.cache[ key]
		}
	}
};
function l( msg){
	$( '#log').html( $( '#log').html() + msg + '<br />');
}
(function(){
var index = -1; // hightlight index
var cache = new Cache;
var last_keywords = '';
var ele_count;
var MIN_LEN	= 3;

var input_ele = $( '#test');
var div_ele = $( '#ac');
var ul_ele = div_ele.find( 'ul:first');

var KEY = {};
KEY.carriage    = 13;
KEY.upArrow     = 38;
KEY.downArrow   = 40;
KEY.leftArrow	= 37;
KEY.rightArrow	= 39;
KEY.esc  	    = 27;

var isEnoughChars = function(){
	var len = $.trim( input_ele.val() ).length;
	if( len < MIN_LEN){
		return false;
	}else{
		div_ele.css( 'display', 'block');
		return true;
	}
};
var render = function( k ){
	index = -1; // rewind highlight index
	var json = cache.get( k, true);
	var q = json[ 0];
	var lists = json[ 1];
	var i = 0, len = lists.length;	
	var li;
	ul_ele.html( '');
	for(; i < len; i++){
		li = $( '<li>');
		ul_ele.append( li);
		li.attr( '_name', i);
		li.attr( '_value', lists[ i]);
		li.html( lists[ i].replace( q, '<b>' + q + '</b>') );
		ul_ele.append( li);
	}
	ele_count = len;
};
var renderList = function(){
	div_ele.css( 'display', 'block');
	var now_keywords = $.trim( input_ele.val() );
	var lists;
	if( last_keywords == now_keywords ){
		return;
	}
	last_keywords = now_keywords;
	if( cache.has( now_keywords ) ){
		render( now_keywords);
	}else{ // ajax to get list
		renderLoading();
		$.get( 'ajax.php?q=' + encodeURIComponent( now_keywords), function( data){
				l( data);
			cache.set( now_keywords, data);
			render( now_keywords);
		}, 'text' );
	}
};
var renderLoading = function(){
	index = 10000; // avoid select 'loading...'
	ul_ele.html( '');
	li = $( '<li>');
	li.html( 'loading...');
	ul_ele.append( li);
};
var submitForm = function(){
	var form_ele = input_ele.parents( 'form');
	if( typeof form_ele[ 0].submit === 'function'){
		form_ele.submit();
	}else{ // in case that input[type="submit"] named "submit"
		form_ele.find( 'input:submit').click();
	}
}
var hideList = function(){
	div_ele.css( 'display', 'none');
}
var setInputValue = function( v){
	input_ele.val( v);
};
var loadSavedValue = function(){
	input_ele.val( last_keywords);
};
input_ele.bind( 'keyup', function( event){
		switch( event.keyCode){
			case KEY.downArrow:
				if( ! isEnoughChars() ){
					return;
				}
				if( index !== -1){ // index == -1: begin
					ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
				}
				index++;
				if( index !== ele_count ){
					ul_ele.find( 'li:eq(' + index + ')').addClass( 'curr');
					setInputValue( ul_ele.find( 'li:eq(' + index + ')').attr( '_value') );
				}else{ // 
					index = -1;
					loadSavedValue();
				}
				l( index);
				break;
			case KEY.upArrow:
				if( ! isEnoughChars() ){
					return
				}
				if( index !== -1 ){
					ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
				}
				index--;
				if( index === -2){
					index = ele_count - 1;
				}
				if( index !== -1){
					ul_ele.find( 'li:eq(' + index + ')').addClass( 'curr');
					setInputValue( ul_ele.find( 'li:eq(' + index + ')').attr( '_value'));
				}else{
					loadSavedValue();
				}
				l( index);
				break;
			case KEY.carriage:
				if( index !== -1){
					setInputValue( ul_ele.find( 'li:eq(' + index + ')').attr( '_value') ); 
					// REMIND: submit automatically
				}
				break;
			case KEY.esc:
			case KEY.leftArrow:
			case KEY.rightArrow:
			case 16: case 17: case 18:case 33: case 34: case 35: case 36: // keys don't send ajax
				break;	
			default:
				if( ! isEnoughChars() ){
					return;
				}
				renderList();
		}
});
input_ele.bind( 'keydown', function( event){
	if( event.keyCode === KEY.esc){
		hideList();
		setTimeout( function(){
			input_ele.focus(); // in case that opera lose focus
		}, 0)
		return false; // in case that ie clear the input
	}
});
ul_ele.bind( 'click', function( event){
	var srcEle = event.target || event.srcElement;		
	var keywords = srcEle.getAttribute( '_value');
	input_ele.val( keywords);	
	submitForm();
});
ul_ele.bind( 'mouseover', function( event){
	var srcEle = event.target || event.srcElement;
	if( srcEle.tagName === 'B'){
		srcEle = srcEle.parentNode;
	}
	if( index !== -1){
		ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
	}
	index = srcEle.getAttribute( '_name');
	ul_ele.find( 'li:eq(' + index + ')').addClass( 'curr');
});
ul_ele.bind( 'mouseout', function( event){
	var srcEle = event.target || event.srcElement;
	if( srcEle.tagName === 'B'){
		srcEle = srcEle.parentNode;
	}
	index = srcEle.getAttribute( '_name');
	if( index != 0 && index != ele_count - 1 ){
		ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
	}
});
setTimeout( function(){
	input_ele.focus(); 
}, 0);
input_ele.bind( 'blur', function(){
	//div_ele.css( 'display', 'none');
});

input_ele.attr( 'autocomplete', 'off');

})();
//BUG: firefox autocomplete = "off" does not work
//]]>
</script>
</body>
</html>
